import mapboxgl, { LngLatBounds } from 'mapbox-gl';
import { ModelObjType } from '../shared/types';
import logger from 'loglevel';
import { ObjectModelManager } from '../model';

/**
 * 跳转选项参数接口
 */
export interface INavigateOptions {
  /** 跳转的持续时间（毫秒） */
  duration?: number;
  /** 目标缩放级别，如果不提供则保持当前缩放级别 */
  zoom?: number;
  /** 动画曲线函数，默认为 'ease-in-out' */
  easing?: (t: number) => number;
  /** 跳转完成后的回调函数 */
  onComplete?: () => void;
  /** 跳转取消时的回调函数 */
  onCancel?: () => void;
}

/**
 * 地图导航工具类 - 提供跳转到指定坐标或对象的功能
 */
export class NavigationManager {
  private map: mapboxgl.Map;
  private mapLoaded = false;
  private objectModelManager: ObjectModelManager;

  constructor(map: mapboxgl.Map, objectModelManager: ObjectModelManager) {
    if (!map) {
      throw new Error('地图实例不能为空');
    }
    if (!objectModelManager) {
      throw new Error('对象模型管理器不能为空');
    }
    this.map = map;
    this.objectModelManager = objectModelManager;
    map.on('load', () => {
      this.mapLoaded = true;
    });
    this.map.on('click', (e) => {
      // 点击事件处理，可以根据需要添加逻辑
      logger.info('地图点击事件:', e.lngLat);
    });
  }

  /**
   * 跳转到指定坐标
   * @param coordinates 目标坐标 [经度, 纬度]
   * @param options 跳转选项
   * @returns Promise，在动画完成时解析
   */
  flyToCoordinates(
    coordinates: [number, number],
    options: INavigateOptions = {},
  ): Promise<void> {
    const { duration = 2000 } = options;
    const flyTo = () => {
      this.map.flyTo({
        center: coordinates,
        zoom: 16,
        duration,
      });
    };
    if (!this.mapLoaded) {
      return new Promise((resolve) => {
        this.map.once('load', () => {
          flyTo();
          resolve();
        });
      });
    }

    return new Promise(() => flyTo());
  }

  /**
   * 跳转到指定对象
   * @param objectId 对象ID
   * @param type 对象类型（可选）
   * @returns Promise，在动画完成并结束突出显示时解析
   */
  async flyToObject(
    objectId: string,
    modelObjType?: ModelObjType,
  ): Promise<void> {
    try {
      const [objType] = (objectId || '').split('.');
      if (!objType) {
        console.error('对象ID格式错误', objectId);
        return;
      }
      if (modelObjType && modelObjType !== objType) {
        console.warn(
          `提供的对象类型 ${modelObjType} 与ID ${objectId} 中的类型 ${objType} 不匹配`,
        );
        return;
      }
      // 获取对象的坐标
      const feature = await this.findObjectById(
        objectId,
        objType as ModelObjType,
      );

      if (!feature) {
        console.error(`找不到ID为${objectId}的对象`);
        return;
      }

      // 从feature中提取坐标
      const coordinates = this.extractCoordinates(feature as GeoJSON.Feature);

      if (!coordinates) {
        console.warn(`无法从对象${objectId}提取坐标`);
        return;
      }

      // 跳转到对象坐标
      await this.flyToCoordinates(coordinates);
    } catch (error) {
      console.error('跳转到对象时出错:', error);
      throw error;
    }
  }

  /**
   * 根据ID查找要素
   * @param id 要素ID
   * @param sourceId 数据源ID
   * @returns GeoJSON要素对象或null
   * @private
   */
  private findObjectById(id: string, objType: ModelObjType) {
    try {
      const feature = this.objectModelManager.getObjectById(id, objType);
      return feature || null;
    } catch (error) {
      console.warn(`查找对象${id}时出错:`, error);
    }

    return null;
  }

  /**
   * 从GeoJSON要素中提取坐标
   * @param feature GeoJSON要素
   * @returns 坐标数组[经度, 纬度]或null
   * @private
   */
  private extractCoordinates(
    feature: GeoJSON.Feature,
  ): [number, number] | null {
    try {
      const geometry = feature.geometry;

      if (!geometry) return null;

      switch (geometry.type) {
        case 'Point':
          // 对于点类型，直接返回坐标
          return [geometry.coordinates[0], geometry.coordinates[1]];

        case 'LineString': {
          // 对于线类型，返回线的中点
          const midIndex = Math.floor(geometry.coordinates.length / 2);
          return [
            geometry.coordinates[midIndex][0],
            geometry.coordinates[midIndex][1],
          ];
        }
        case 'Polygon':
          // 对于多边形，返回第一个多边形的中心点
          // 这只是一个简化的实现，实际上应计算质心
          if (geometry.coordinates && geometry.coordinates[0]) {
            let sumX = 0;
            let sumY = 0;
            const points = geometry.coordinates[0];

            for (const point of points) {
              sumX += point[0];
              sumY += point[1];
            }

            return [sumX / points.length, sumY / points.length];
          }
          break;

        default:
          // 对于其他类型，尝试从属性中获取坐标
          if (feature.properties && feature.properties.coordinates) {
            const coords = feature.properties.coordinates;
            if (Array.isArray(coords) && coords.length >= 2) {
              return [coords[0], coords[1]];
            }
          }
      }
    } catch (error) {
      console.warn('提取坐标时出错:', error);
    }

    return null;
  }

  /**
   * 使地图自动缩放并平移以显示所有内容
   * @param options 导航选项
   * @returns Promise，在动画完成时解析
   */
  async fitAllContent(options: INavigateOptions = {}): Promise<void> {
    const { duration = 1500, onComplete, onCancel } = options;

    const fitBounds = (bounds: LngLatBounds) => {
      this.map.fitBounds(
        bounds,
        {
          padding: 50,
          duration,
          animate: true,
        },
        {
          onComplete: () => {
            if (onComplete) onComplete();
          },
          onCancel: () => {
            if (onCancel) onCancel();
          },
        },
      );
    };
    /**
     * 确保在地图加载后执行fitBounds
     * @param bounds 边界框
     */
    const runFitBoundsAfterLoad = (bounds: LngLatBounds) => {
      if (!this.mapLoaded) {
        return new Promise((resolve) => {
          this.map.once('load', () => {
            fitBounds(bounds);
            resolve(null);
          });
        });
      } else {
        fitBounds(bounds);
      }
    };

    try {
      // 尝试从缓存获取全局边界信息
      const cachedBounds = await this.objectModelManager.getModelGlobalBounds();

      // 如果有缓存的边界信息，直接使用
      if (cachedBounds) {
        logger.info('使用缓存的全局边界信息进行适配');

        // 创建边界框
        const bounds = new mapboxgl.LngLatBounds(
          [cachedBounds.minLng, cachedBounds.minLat],
          [cachedBounds.maxLng, cachedBounds.maxLat],
        );

        // 使用边界调整地图
        await runFitBoundsAfterLoad(bounds);
        return;
      }

      logger.info('未找到缓存的边界信息，从模型对象计算边界');

      // 如果没有缓存的边界信息，回退到原来的方式
      // 获取所有模型对象信息
      const objGisInfoList =
        await this.objectModelManager.getModelObjectGisInfo();

      // 收集所有要素
      const allFeatures: GeoJSON.Feature[] = [];
      for (const objGisInfo of objGisInfoList) {
        if (objGisInfo && objGisInfo.data && Array.isArray(objGisInfo.data)) {
          allFeatures.push(...(objGisInfo.data as any[]));
        }
      }

      if (!allFeatures || allFeatures.length === 0) {
        console.warn('没有找到任何对象，无法适配内容');
        return;
      }

      // 计算所有要素的边界框
      const bounds = this.calculateBoundsForFeatures(
        allFeatures as GeoJSON.Feature[],
      );

      if (!bounds) {
        console.warn('无法计算内容边界，适配失败');
        return;
      }

      // 使用动画效果使地图适应这个边界框
      runFitBoundsAfterLoad(bounds);
    } catch (error) {
      console.error('适配全部内容时出错:', error);
    }
  }

  /**
   * 计算多个要素的边界框
   * @param features GeoJSON要素数组
   * @returns 边界框或null
   * @private
   */
  private calculateBoundsForFeatures(
    features: GeoJSON.Feature[],
  ): mapboxgl.LngLatBounds | null {
    try {
      // 创建一个空的边界框
      let bounds: mapboxgl.LngLatBounds | null = null;

      // 遍历所有要素
      for (const feature of features) {
        const coordinates = this.getCoordinatesFromFeature(feature);

        if (coordinates && coordinates.length > 0) {
          // 对于每个坐标点，扩展边界框
          for (const coord of coordinates) {
            if (!bounds) {
              // 如果边界框还不存在，用第一个坐标初始化它
              bounds = new mapboxgl.LngLatBounds(
                [coord[0], coord[1]],
                [coord[0], coord[1]],
              );
            } else {
              // 扩展边界框以包含新坐标
              bounds.extend([coord[0], coord[1]]);
            }
          }
        }
      }

      return bounds;
    } catch (error) {
      console.error('计算边界框时出错:', error);
      return null;
    }
  }

  /**
   * 从要素中提取所有坐标点
   * @param feature GeoJSON要素
   * @returns 坐标点数组或null
   * @private
   */
  private getCoordinatesFromFeature(
    feature: GeoJSON.Feature,
  ): [number, number][] | null {
    try {
      const geometry = feature.geometry;
      if (!geometry) return null;

      const coords: [number, number][] = [];

      switch (geometry.type) {
        case 'Point':
          coords.push([geometry.coordinates[0], geometry.coordinates[1]]);
          break;

        case 'LineString':
          for (const coord of geometry.coordinates) {
            coords.push([coord[0], coord[1]]);
          }
          break;

        case 'Polygon':
          for (const ring of geometry.coordinates) {
            for (const coord of ring) {
              coords.push([coord[0], coord[1]]);
            }
          }
          break;

        case 'MultiPoint':
          for (const point of geometry.coordinates) {
            coords.push([point[0], point[1]]);
          }
          break;

        case 'MultiLineString':
          for (const line of geometry.coordinates) {
            for (const coord of line) {
              coords.push([coord[0], coord[1]]);
            }
          }
          break;

        case 'MultiPolygon':
          for (const polygon of geometry.coordinates) {
            for (const ring of polygon) {
              for (const coord of ring) {
                coords.push([coord[0], coord[1]]);
              }
            }
          }
          break;

        case 'GeometryCollection':
          // 递归处理几何集合中的每个几何体
          for (const geom of geometry.geometries) {
            const geomFeature: GeoJSON.Feature = {
              type: 'Feature',
              properties: feature.properties,
              geometry: geom,
            };
            const subCoords = this.getCoordinatesFromFeature(geomFeature);
            if (subCoords) {
              coords.push(...subCoords);
            }
          }
          break;
      }

      return coords;
    } catch (error) {
      console.error('从要素中提取坐标时出错:', error);
      return null;
    }
  }
}
